﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.Collections;
using AmiEmuLib.Host;
using System.IO;
using AmiEmuLib.Configuration.EventBindingConfig;
using AmiEmuLib.Configuration.AmigaEventsConfig;
using AmiEmuLib.Host.HostInput;
using log4net;
using TomanuExtensions;

namespace AmiEmuLib.Configuration.EventBindingConfig
{
    /// <summary>
    /// List of devices binding events. Defined by user.
    /// </summary>
    [DebuggerDisplay("Count = {List.Count}")]
    public static class EventsBindingList
    {
        /// <summary>
        /// Logger.
        /// </summary>
        private static readonly ILog Logger = LogManager.GetLogger(typeof(EventsBindingList));

        /// <summary>
        /// Place where we keep our bindings.
        /// </summary>
        private static readonly string BindingsPath = HostEnvironment.SettingsDir + "EventsBindings.xml";

        /// <summary>
        /// All bindings (configuration + default).
        /// </summary>
        private static readonly List<EventsBinding> m_list = new List<EventsBinding>();

        /// <summary>
        /// Static constructor.
        /// </summary>
        static EventsBindingList()
        {
            HostEnvironment.HostWindowDetached += SaveToXml;

            Reload();
        }

        /// <summary>
        /// Reload bindings.
        /// </summary>
        internal static void Reload()
        {
            m_list.Clear();
            m_list.AddRange(DefaultEventsBindings.List);
            Sort();

            if (m_list.Count == 0)
            {
                Logger.Error("DefaultEventsBindings empty, called from its static constructor ?");
                throw new Exception();
            }

            if (new FileInfo(BindingsPath).Exists)
                Load(BindingsPath);
        }

        /// <summary>
        /// Load bindings from file.
        /// </summary>
        /// <param name="a_fileName"></param>
        internal static void Load(string a_fileName)
        {
            using (var fs = new FileStream(BindingsPath, FileMode.Open, FileAccess.Read))
                Load(fs);
        }

        /// <summary>
        /// Load bindings from stream.
        /// </summary>
        /// <param name="a_stream"></param>
        internal static void Load(Stream a_stream)
        {
            m_list.Clear();
            m_list.AddRange(DefaultEventsBindings.List);

            XmlReaderExtensions.ReadXml(a_stream, (reader) =>
            {
                if (reader.IsEmptyElement)
                {
                    reader.ReadStartElement();
                    return;
                }

                reader.ReadStartElement(typeof(EventsBindingList).Name);

                while (reader.IsStartElement())
                    m_list.Add(new EventsBinding(reader, false));

                reader.ReadEndElement();
            });

            Sort();
        }

        /// <summary>
        /// Add new bindings.
        /// </summary>
        /// <param name="a_name">
        /// Name.
        /// </param>
        /// <param name="a_source">
        /// Source of bindings events.
        /// </param>
        /// <returns></returns>
        public static EventsBinding Add(string a_name, EventsBinding a_source)
        {
            a_name = a_name ?? "";

            a_name = a_name.Trim();

            if (m_list.Any(b => b.Name.ToLower() == a_name.ToLower()))
            {
                Logger.Error("Item with this name alreasy exists");
                return null;
            }

            if (a_name == "")
            {
                Logger.Error("Name is empty");
                return null;
            }

            EventsBinding result = new EventsBinding(a_name, a_source);
            m_list.Add(result);
            Sort();

            return result;
        }

        /// <summary>
        /// Sort item by DisplayName.
        /// </summary>
        private static void Sort()
        {
            m_list.Sort((el1, el2) => String.Compare(el1.Name, el2.Name));
        }

        /// <summary>
        /// Saves bindings to xml.
        /// </summary>
        public static void SaveToXml()
        {
            XmlWriterExtensions.WriteXml(BindingsPath, (writer) =>
            {
                writer.WriteStartElement(typeof(EventsBindingList).Name);

                foreach (var events_binding in m_list.Where(b => !b.ReadOnly))
                    events_binding.WriteXml(writer);

                writer.WriteEndElement();
            });
        }

        /// <summary>
        /// All bindings (configuration + default).
        /// </summary>
        public static IEnumerable<EventsBinding> List
        {
            get
            {
                return m_list;
            }
        }

        /// <summary>
        /// Remove events binding.
        /// </summary>
        /// <param name="a_events_binding"></param>
        public static bool Remove(EventsBinding a_events_binding)
        {
            if (a_events_binding == null)
            {
                Logger.Error("a_events_binding is null");
                return false;
            }

            if (a_events_binding.ReadOnly)
            {
                Logger.Error("Trying to remove read only events binding");
                return false;
            }

            if (!m_list.Contains(a_events_binding))
            {
                Logger.Error("Trying to remove events binding: not in list");
                return false;
            }

            m_list.Remove(a_events_binding);

            return true;
        }

        /// <summary>
        /// Get bindings by name.
        /// </summary>
        /// <param name="a_name"></param>
        /// <returns></returns>
        public static EventsBinding GetByName(string a_name)
        {
            return m_list.FirstOrDefault(b => b.Name == a_name);
        }
    }
}
